/*
 * Copyright (C) 2020 Jimmy Hogoboom
 *
 * This file is part of Limitlist.
 *
 * Limitlist is free software: you can redistribute it and/or modify
 * it under the terms of the Affero GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Limitlist is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Affero GNU General Public License for more details.
 *
 * You should have received a copy of the Affero GNU General Public License
 * along with Limitlist.  If not, see <https://www.gnu.org/licenses/>.
 */

// SPDX-License-Identifier: AGPL-3.0-or-later

import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { mocked } from 'ts-jest/utils';
import ProductListManager, { IProductListManagedComponentProps } from './ProductListManager';
import storage from '../../storage';
import { IProductListItem } from 'core/types/IProductListItem';

jest.mock('../../storage');
const mockedStorage = mocked(storage, true);

let testList = [
  {
    id: 'testItemId',
    name: 'Test Name',
    price: 1.99,
    quantity: 1,
  } as IProductListItem,
  {
    id: 'testItemId2',
    name: 'Second Test Name',
    price: 4.99,
    quantity: 2,
  } as IProductListItem,
];

const updatedItem = {
  id: 'testItemId',
  name: 'Test Name Updated',
  price: 3,
  quantity: 4,
} as IProductListItem;

const Display: React.FC<{ value: string } & IProductListManagedComponentProps> = ({
  value,
  items,
  editingItemIds,
  addItem,
  deleteItem,
  updateItem,
  moveItem,
}) => {
  return (
    <>
      <div>{value}</div>
      {items?.map(i => (
        <div
          key={i.id}
        >
          {editingItemIds?.includes(i.id) && 'Editing'}
          <span>Test Item</span>
          <span data-testid="ItemName">{i.name}</span>
        </div>
      ))}
      <button onClick={addItem}>Add Item</button>
      <button onClick={() => deleteItem!(testList[0])}>Delete Item</button>
      <button onClick={() => updateItem!(updatedItem)}>Update Item</button>
      <button onClick={() => moveItem!(0, 1)}>Move Item</button>
    </>
  );
};

const testRender = () => render(
  <ProductListManager
    listId="testId"
    storage={mockedStorage}
  >
    <Display value={'Test Value'} />
  </ProductListManager>
);

beforeEach(() => {
  mockedStorage.Lists.find.mockClear();
  mockedStorage.Lists.set.mockClear();

  mockedStorage.Lists.find.mockImplementationOnce(async () => {
    return testList;
  });
  mockedStorage.Lists.set.mockImplementationOnce(() => {});
});

test('manager passes all props', async () => {
  const { findByText } = testRender();

  const display = await findByText('Test Value');
  expect(display).toBeInTheDocument();
});

test('manager provides list', async () => {
  const { findByText } = testRender();

  const display = await findByText('Test Name');
  expect(display).toBeInTheDocument();
});

test('manager adds item and updates storage', async () => {
  const { findByText, findAllByText } = testRender();

  const itemsBefore = await findAllByText('Test Item');
  expect(itemsBefore.length).toBe(testList.length);

  const addButton = await findByText('Add Item');
  fireEvent.click(addButton);

  expect(mockedStorage.Lists.set).toHaveBeenCalledTimes(1);

  const itemsAfter = await findAllByText('Test Item');
  expect(itemsAfter.length).toBe(testList.length + 1);
});

test('added item is included in list of editing items', async () => {
  const { findByText } = testRender();
  const addButton = await findByText('Add Item');

  fireEvent.click(addButton);

  const editingItem = await findByText('Editing');
  expect(editingItem).toBeInTheDocument();
});

test('manager deletes item and updates storage', async () => {
  const { findByText, queryAllByText } = testRender();
  const deleteButton = await findByText('Delete Item');

  fireEvent.click(deleteButton);

  expect(mockedStorage.Lists.set).toHaveBeenCalledTimes(1);

  const itemsAfter = queryAllByText('Test Item');
  expect(itemsAfter.length).toBe(testList.length - 1);
});

test('manager updates item and updates storage', async () => {
  const { findByText } = testRender();
  const updateButton = await findByText('Update Item');

  fireEvent.click(updateButton);

  expect(mockedStorage.Lists.set).toHaveBeenCalledTimes(1);

  const item = await findByText('Test Name Updated');
  expect(item).toBeInTheDocument();
});

test('manage reorders items and updates storage', async () => {
  const { findByText, findAllByTestId } = testRender();
  const moveButton = await findByText('Move Item');

  const itemsBefore = await findAllByTestId('ItemName');
  expect(itemsBefore[0]).toHaveTextContent('Test Name');

  fireEvent.click(moveButton);

  expect(mockedStorage.Lists.set).toHaveBeenCalledTimes(1);

  const itemsAfter = await findAllByTestId('ItemName');
  expect(itemsAfter[0]).toHaveTextContent('Second Test Name');
});
